package com.dlc.apk.api

import com.dlc.apk.utils.IOUtils
import com.dlc.apk.utils.LogPlus
import com.dlc.apk.utils.StringUtil
import okhttp3.*
import okio.Buffer
import java.io.IOException
import java.net.NoRouteToHostException
import java.net.SocketTimeoutException
import java.net.UnknownHostException
import java.nio.charset.Charset
import java.util.concurrent.TimeUnit

class HttpLogInterceptor(private val tag: String) : Interceptor {
    private fun log(message: String) {
        LogPlus.e(tag, message)
    }

    @Throws(IOException::class)
    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        //请求日志拦截
        val logStringBuilder = StringBuilder()
        logForRequest(request, chain.connection(), logStringBuilder)

        //执行请求，计算请求时间
        val startNs = System.nanoTime()
        val response: Response
        response = try {
            chain.proceed(request)
        } catch (e: Exception) {
            log("<-- HTTP FAILED: $e")
            if(e is NoRouteToHostException || e is UnknownHostException){
                throw Throwable("网络异常", e)
            }else if(e is SocketTimeoutException){
                throw Throwable("网络连接超时", e)
            }else{
                throw e
            }
        }
        val tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs)

        //响应日志拦截
        return logForResponse(response, tookMs, logStringBuilder)
    }

    @Throws(IOException::class)
    private fun logForRequest(request: Request, connection: Connection?, logStringBuilder: StringBuilder) {
        val requestBody = request.body()
        val hasRequestBody = requestBody != null
        val protocol = if (connection != null) connection.protocol() else Protocol.HTTP_1_1
        try {
            logStringBuilder.append(request.method()).append(' ').append(request.url()).append(' ').append(protocol).append("\n")
            if (hasRequestBody) {
                if (isPlaintext(requestBody!!.contentType())) {
                    logStringBuilder.append("request body -> ").append(bodyToString(request))
                } else {
                    logStringBuilder.append("body: maybe [binary body], omitted!")
                }
            }
        } catch (e: Exception) {
            LogPlus.e(LogPlus.getStackTraceInfo(e))
        } finally {
            logStringBuilder.append("\n")
        }
    }

    private fun logForResponse(response: Response, tookMs: Long, logStringBuilder: StringBuilder): Response {
        val builder = response.newBuilder()
        val clone = builder.build()
        var responseBody = clone.body()
        try {
            if (responseBody == null) return response
            if (isPlaintext(responseBody.contentType())) {
                val bytes = IOUtils.toByteArray(responseBody.byteStream())
                /*val contentType = responseBody.contentType()*/
                /*val body = String(bytes, getCharset(contentType)!!)
                logStringBuilder.append("response body -> ").append(body)*/
                logStringBuilder.append("response body -> ").append(StringUtil.convertToString(bytes))
                responseBody = ResponseBody.create(responseBody.contentType(), bytes)
                return response.newBuilder().body(responseBody).build()
            } else {
                logStringBuilder.append("response body: maybe [binary body], omitted!")
            }
        } catch (e: Exception) {
            LogPlus.e(LogPlus.getStackTraceInfo(e))
        } finally {
            logStringBuilder.append("\n-- took").append("(").append(tookMs).append("ms)")
            log(logStringBuilder.toString())
        }
        return response
    }

    private fun bodyToString(request: Request): String {
        try {
            val copy = request.newBuilder().build()
            val body = copy.body() ?: return ""
            val buffer = Buffer()
            body.writeTo(buffer)
            val charset = getCharset(body.contentType())
            return buffer.readString(charset)
        } catch (e: Exception) {
            LogPlus.e(LogPlus.getStackTraceInfo(e))
        }
        return ""
    }

    companion object {
        private val UTF8 = Charset.forName("UTF-8")
        fun getCharset(contentType: MediaType?): Charset? {
            var charset = if (contentType != null) contentType.charset(UTF8) else UTF8
            if (charset == null) charset = UTF8
            return charset
        }

        /**
         * Returns true if the body in question probably contains human readable text. Uses a small sample
         * of code points to detect unicode control characters commonly used in binary file signatures.
         */
        fun isPlaintext(mediaType: MediaType?): Boolean {
            if (mediaType == null) return false
            if (mediaType.type() == "text") {
                return true
            }
            var subtype = mediaType.subtype()
            subtype = subtype.toLowerCase()
            return subtype.contains("x-www-form-urlencoded") || subtype.contains("json") || subtype.contains("xml") || subtype.contains("html")
        }
    }
}